using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using MinesweeperPlus.Scripts.Abstractions.Enums;
using MinesweeperPlus.Scripts.Abstractions.Interfaces;
using MinesweeperPlus.Scripts.Configs;

namespace MinesweeperPlus.Scripts.Objects
{
    public class Level : ILevel
    {
        #region Actions
        public Action<Cell> OnCellOpen;
        public Action<int> OnNumberOfUnsetFlagsChange;
        #endregion

        #region Constants
        private const int CELL_OPENING_RADIUS = 1;
        #endregion

        #region Variables
        private IObjectPool<Cell> _pool;
        private Cell[,] _level;
        private List<Cell> _closedNonMinedCells;
        private Vector2 _offset;
        private Vector2Int[] _mineCellsPositions;
        private int _numberOfUnsetFlags; 
        #endregion
        #region Propertions
        public LevelConfig Config { get; private set; } 
        public int NumberOfUnsetFlags 
        { 
            get => _numberOfUnsetFlags;
            private set
            {
                if (_numberOfUnsetFlags == value)
                    return;

                _numberOfUnsetFlags = value;
                OnNumberOfUnsetFlagsChange?.Invoke(_numberOfUnsetFlags);
            } 
        }
        public int NumberOfNonMineCells { get; private set; }
        #endregion

        #region Level Methods
        public Level(LevelConfig config, IObjectPool<Cell> pool)
        {
            Config = config;
            _pool = pool;
            _level = new Cell[Config.Width, Config.Height];
            _offset = new Vector2(Config.Width - 1, Config.Height - 1) / -2f;
            NumberOfUnsetFlags = Config.NumberOfMines;
            _mineCellsPositions = new Vector2Int[NumberOfUnsetFlags];
            _closedNonMinedCells = new();
        }

        public void Create()
        {
            CreateCells();

            CreateMineCellsPositions();

            CreateNumbersOnLevel();
            CreateMinesOnLevel();
        }
        public void Clear()
        {
            foreach (var cell in _level)
                _pool.Release(cell);
            _closedNonMinedCells.Clear();
        } 
        public void Reset()
        {
            Clear();
            Create();
        }
        
        public void SwitchFlagInWorldPoint(Vector2 worldPosition)
        {
            try
            {
                var levelPosition = WorldToLevelPoint(worldPosition);
                var cell = GetCell(levelPosition);
                if (cell.IsOpened) 
                {
                    if (GetNumberOfNeighboringCellsFlags(levelPosition) >= (int)cell.BodyType)
                        TryOpenNeighboringCells(levelPosition);
                }
                else if (cell.IsFlagged)
                {
                    cell.Unflag();
                    NumberOfUnsetFlags++;
                }
                else if (NumberOfUnsetFlags > 0)
                {
                    cell.Flag();
                    NumberOfUnsetFlags--;
                }
            }
            catch { }
        }
        public void OpenCellInWorldPoint(Vector2 worldPosition)
        {
            try
            {
                var levelPosition = WorldToLevelPoint(worldPosition);
                var cell = GetCell(levelPosition);
                if (cell.IsOpened && 
                    cell.BodyType != CellBodyType.None &&
                    GetNumberOfNeighboringCellsFlags(levelPosition) >= (int)cell.BodyType)
                {
                    TryOpenNeighboringCells(levelPosition);
                }
                else
                {
                    TryOpenCellAndNeighboringCells(levelPosition);
                }
            }
            catch { }
        }

        public void OpenCellsWithMine()
        {
            foreach (var position in _mineCellsPositions)
                GetCell(position).Open();
        }
        
        public void OpenRandomNonMinedCell()
        {
            var randomNumber = UnityEngine.Random.Range(0, _closedNonMinedCells.Count);
            var randomCell = _closedNonMinedCells[randomNumber];
            var randomCellPosition = randomCell.Transform.position;

            if (randomCell.IsFlagged)
            {
                randomCell.Unflag();
                _numberOfUnsetFlags++;
            }
            OpenCellInWorldPoint(randomCellPosition);
        }
        #endregion

        #region Other Methods
        private Vector2Int WorldToLevelPoint(Vector2 worldPosition)
        {
            worldPosition -= _offset;
            var roundedPosition = new Vector2Int(
                Mathf.RoundToInt(worldPosition.x),
                Mathf.RoundToInt(worldPosition.y));
            return roundedPosition;
        }

        private void TryOpenNeighboringCells(Vector2Int cellPosition)
        {
            try
            {
                var cell = GetCell(cellPosition);
                for (int x = -CELL_OPENING_RADIUS; x <= CELL_OPENING_RADIUS; x++)
                    for (int y = -CELL_OPENING_RADIUS; y <= CELL_OPENING_RADIUS; y++)
                        if (x != 0 || y != 0)
                            try
                            {
                                TryOpenCellAndNeighboringCells(
                                    cellPosition + new Vector2Int(x, y));
                            }
                            catch { }
            }
            catch { }
        }
        private void TryOpenCellAndNeighboringCells(Vector2Int cellPosition)
        {
            try
            {
                var cell = GetCell(cellPosition);
                if (cell.IsFlagged || cell.IsOpened)
                    return;

                if (cell.BodyType != CellBodyType.Mine)
                    NumberOfNonMineCells--;

                cell.Open();
                _closedNonMinedCells.Remove(cell);
                OnCellOpen?.Invoke(cell);

                if (cell.BodyType == CellBodyType.None)
                    TryOpenNeighboringCells(cellPosition);
            }
            catch { }
        }
        
        private int GetNumberOfNeighboringCellsFlags(Vector2Int cellPosition)
        {
            int number = 0;
            try
            {
                if (GetCell(cellPosition).BodyType == CellBodyType.None)
                    return number; 

                for (int x = -CELL_OPENING_RADIUS; x <= CELL_OPENING_RADIUS; x++)
                    for (int y = -CELL_OPENING_RADIUS; y <= CELL_OPENING_RADIUS; y++)
                        try
                        {
                            if (GetCell(cellPosition + new Vector2Int(x, y)).IsFlagged)
                                number++;
                        }
                        catch { }
            }
            catch { }
            return number;
        }
        
        private Cell GetCell(Vector2Int positionOnLevel) => 
            _level[positionOnLevel.x, positionOnLevel.y];

        private void CreateCells()
        {
            for (int x = 0; x < Config.Width; x++)
                for (int y = 0; y < Config.Height; y++)
                {
                    var cell = _pool.Get();
                    cell.Transform.position = new Vector2(x, y) + _offset;
                    _level[x, y] = cell;
                    _closedNonMinedCells.Add(cell);
                }
        }
        private void CreateMineCellsPositions()
        {
            var width = Config.Width;
            var height = Config.Height;

            NumberOfUnsetFlags = Config.NumberOfMines;
            OnNumberOfUnsetFlagsChange?.Invoke(NumberOfUnsetFlags);

            NumberOfNonMineCells = width * height - NumberOfUnsetFlags;

            var nonMinedCellsPositions = new List<Vector2Int>(width * height);
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                    nonMinedCellsPositions.Add(new Vector2Int(x, y));

            for (int i = 0; i < NumberOfUnsetFlags; i++)
            {
                var randomPosition = nonMinedCellsPositions[
                    UnityEngine.Random.Range(0, nonMinedCellsPositions.Count)];
                nonMinedCellsPositions.Remove(randomPosition);
                
                _mineCellsPositions[i] = randomPosition;
            }
        }

        private void CreateNumbersOnLevel()
        {
            foreach (var position in _mineCellsPositions)
                for (int x = -CELL_OPENING_RADIUS; x <= CELL_OPENING_RADIUS; x++)
                    for (int y = -CELL_OPENING_RADIUS; y <= CELL_OPENING_RADIUS; y++)
                        try
                        {
                            var cell = GetCell(position + new Vector2Int(x, y));
                            if (cell.BodyType != CellBodyType.Mine)
                                cell.NextBodyType();
                        }
                        catch { }
        }
        private void CreateMinesOnLevel()
        {
            foreach (var position in _mineCellsPositions)
            {
                var cell = GetCell(position);
                cell.BodyType = CellBodyType.Mine;
                _closedNonMinedCells.Remove(cell);
            }
        }
        #endregion
    }
}
